`include "all_define.vh"

module id_stage (
  input clk,
  input rst_n,

  input                             ctrl_id_flush,
  input                             ctrl_id_stall,
  input                             ctrl_id_rs1_bypass_en,
  input [31:0]                      ctrl_id_rs1_bypass_data,
  input                             ctrl_id_rs2_bypass_en,
  input [31:0]                      ctrl_id_rs2_bypass_data,

  input                             if_id_vld,
  input      [31:0]                 if_id_inst,
  input      [31:0]                 if_id_pc,

  input                             wb_id_rd_wena,
  input      [4:0]                  wb_id_rd,
  input      [31:0]                 wb_id_rd_data,

  output reg                        id_exe_vld,
  output reg                        id_exe_is_lui,
  output reg                        id_exe_is_bench,
  output reg                        id_exe_is_jal,
  output reg                        id_exe_is_jalr,
  output reg [31:0]                 id_exe_bench_target,
  output reg [31:0]                 id_exe_pc,
  output reg [31:0]                 id_exe_pc_inc,
  output reg                        id_exe_op1_is_pc,
  output reg                        id_exe_op2_is_imm,
  output reg                        id_exe_is_unsigned,
  output reg [`ALU_TYPE_WIDTH-1:0]  id_exe_alu_type,
  output reg [`ALU_OPSEL_WIDTH-1:0] id_exe_alu_opsel,
  output reg [31:0]                 id_exe_imm_data,
  output     [31:0]                 id_exe_rs1_data,
  output     [31:0]                 id_exe_rs2_data,

  output reg                        id_exe_mem_ena,
  output reg [`MEM_DTYPE_WIDTH-1:0] id_exe_mem_dtype,
  output reg                        id_exe_mem_rwn,

  output reg                        id_exe_wb_ena,
  output reg [4:0]                  id_exe_wb_rd,

  output reg                        id_jmp_req,
  output reg [31:0]                 id_jmp_target,

  output                            id_rs1_bypass_vld,
  output [4:0]                      id_rs1_bypass,
  output                            id_rs2_bypass_vld,
  output [4:0]                      id_rs2_bypass
);

  wire [6:0]  opcode;
  wire        is_lui;
  wire        is_auipc;
  wire        is_jal;
  wire        is_jalr;
  wire        is_bench;
  wire        is_load;
  wire        is_store;
  wire        is_immal;
  wire        is_regal;
  wire        is_fence;
  wire        is_env;

  wire        is_shift;
  wire        is_mem_op;

  wire [19:0] imm_signed_expand20;
  wire [2:0]  func3;
  wire [6:0]  func7;
  wire [4:0]  rs1;
  wire [4:0]  rs2;
  wire        bench_unsigned;
  wire        immal_unsigned;
  wire        regal_unsigned;
  wire        need_wb;

  wire op1_is_pc;
  wire op2_is_imm;

  wire        update_en;

  reg  [31:0] imm_data_s;


  assign func3 = if_id_inst[14:12];
  assign func7 = if_id_inst[31:25];
  assign rs1 = if_id_inst[19:15];
  assign rs2 = if_id_inst[24:20];

  regfile #(
    .N_REGS(32)
  ) regfile_inst (
    .clk(clk),
    .rst_n(rst_n),
    .rs1(rs1),
    .rs2(rs2),
    .ctrl_id_stall(ctrl_id_stall),
    .rs1_bypass_en(ctrl_id_rs1_bypass_en),
    .rs1_bypass_data(ctrl_id_rs1_bypass_data),
    .rs2_bypass_en(ctrl_id_rs2_bypass_en),
    .rs2_bypass_data(ctrl_id_rs2_bypass_data),
    .rs1_data(id_exe_rs1_data),
    .rs2_data(id_exe_rs2_data),
    .rd_wena(wb_id_rd_wena),
    .rd(wb_id_rd),
    .rd_data(wb_id_rd_data)
  );

  assign id_rs1_bypass = rs1;
  assign id_rs2_bypass = rs2;
  assign id_rs1_bypass_vld = (rs1 != 0);
  assign id_rs2_bypass_vld = (rs2 != 0);
  //assign id_rs1_bypass_vld = (rs1 != 0) && (~op1_is_pc);
  //assign id_rs2_bypass_vld = (rs2 != 0) && (~op2_is_imm);

  assign opcode = if_id_inst[6:0];

  assign is_lui   = (opcode == `OPCODE_LUI);
  assign is_auipc = (opcode == `OPCODE_AUIPC);
  assign is_jal   = (opcode == `OPCODE_JAL);
  assign is_jalr  = (opcode == `OPCODE_JALR);
  assign is_bench = (opcode == `OPCODE_BENCH);
  assign is_load  = (opcode == `OPCODE_LOAD);
  assign is_store = (opcode == `OPCODE_STORE);
  assign is_immal = (opcode == `OPCODE_IMMAL);
  assign is_regal = (opcode == `OPCODE_REGAL);
  assign is_fence = (opcode == `OPCODE_FENCE);
  assign is_env   = (opcode == `OPCODE_ENV);

  assign is_shift = is_immal & (func3[1:0] == 2'b01);
  assign is_mem_op = (is_load | is_store);

  assign bench_unsigned = func3[1];
  assign immal_unsigned = (func3 == `FUNC3_SLTU);
  assign regal_unsigned = immal_unsigned;
  assign need_wb = (is_lui | is_auipc | is_jal |
                    is_jalr | is_load | is_immal |
                    is_regal | is_env);

  assign imm_signed_expand20 = {20{if_id_inst[31]}};

  assign op1_is_pc = is_auipc;
  assign op2_is_imm = (is_lui | is_auipc |
                       is_jalr |
                       is_load | is_store |
                       is_immal );

  assign update_en = ~ctrl_id_stall;

  ////////////////////////
  // immediate generate //
  ////////////////////////
  always @(*) begin
    if (is_lui | is_auipc) begin
      imm_data_s = {if_id_inst[31:12], 12'b0};
    end else if (is_jal) begin
      imm_data_s = {imm_signed_expand20[10:0], if_id_inst[31], if_id_inst[19:12], if_id_inst[20], if_id_inst[30:21], 1'b0};
    end else if (is_jalr | is_load | is_immal) begin
      imm_data_s = {imm_signed_expand20, if_id_inst[31:20]};
    end else if (is_bench) begin
      imm_data_s = {imm_signed_expand20[18:0], if_id_inst[31], if_id_inst[7], if_id_inst[30:25], if_id_inst[11:8], 1'b0};
    end else if (is_store) begin
      imm_data_s = {{20{if_id_inst[31]}}, if_id_inst[31:25], if_id_inst[11:7]};
    end else if (is_shift) begin
      imm_data_s = {27'b0, if_id_inst[24:20]};
    end else begin
      imm_data_s = 32'b0;
    end
  end

  ///////////////////////////
  // simple pc calculation //
  ///////////////////////////
  wire [31:0] pc_jmp_target;
  wire [31:0] pc_inc;
  assign pc_jmp_target = if_id_pc + imm_data_s;
  assign pc_inc        = if_id_pc + 32'h4;

  //////////////////////////
  // generate decode info //
  //////////////////////////
  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_vld <= 1'b0;
    end else if (update_en) begin
      id_exe_vld <= ~ctrl_id_flush & if_id_vld;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_op1_is_pc <= 1'b0;
    end else if (update_en) begin
      id_exe_op1_is_pc <= op1_is_pc;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_op2_is_imm <= 1'b0;
    end else if (update_en) begin
      id_exe_op2_is_imm <= op2_is_imm;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_is_unsigned <= 1'b0;
    end else if (update_en) begin
      id_exe_is_unsigned <= ((is_bench & bench_unsigned) |
                 (is_regal & regal_unsigned)|
                 (is_immal & immal_unsigned));
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_is_bench <= 1'b0;
    end else if (update_en) begin
      id_exe_is_bench <= is_bench;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_is_lui <= 1'b0;
    end else if (update_en) begin
      id_exe_is_lui <= is_lui;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_is_jal <= 1'b0;
      id_exe_is_jalr <= 1'b0;
    end else if (update_en) begin
      id_exe_is_jal <= is_jal;
      id_exe_is_jalr <= is_jalr;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_alu_type  <= 'h0;
      id_exe_alu_opsel <= 'h0;
    end else if (update_en) begin
      if (is_auipc | is_jalr) begin
        id_exe_alu_type  <= `ALU_TYPE_ARITH;
        id_exe_alu_opsel <= `ALU_ARITH_ADD;
      end else if (is_bench) begin
        id_exe_alu_type  <= `ALU_TYPE_CMP;
        case ({if_id_inst[14], if_id_inst[12]})
          'h0: id_exe_alu_opsel     <= `ALU_CMP_EQ;
          'h1: id_exe_alu_opsel     <= `ALU_CMP_NE;
          'h2: id_exe_alu_opsel     <= `ALU_CMP_LT;
          'h3: id_exe_alu_opsel     <= `ALU_CMP_GE;
          default: id_exe_alu_opsel <= 'h0;
        endcase
      end else if (is_mem_op) begin
        id_exe_alu_type  <= `ALU_TYPE_ARITH;
        id_exe_alu_opsel <= `ALU_ARITH_ADD;
      end else if (is_immal | is_regal) begin
        if (func3 == `FUNC3_ADD) begin
          id_exe_alu_type  <= `ALU_TYPE_ARITH;
          id_exe_alu_opsel <= `ALU_ARITH_ADD;
        end else if (func3 == `FUNC3_SLT || func3 == `FUNC3_SLTU) begin
          id_exe_alu_type  <= `ALU_TYPE_CMP;
          id_exe_alu_opsel <= `ALU_CMP_LT;
        end else if (func3 == `FUNC3_XOR) begin
          id_exe_alu_type  <= `ALU_TYPE_LOGIC;
          id_exe_alu_opsel <= `ALU_LOGIC_XOR;
        end else if (func3 == `FUNC3_OR) begin
          id_exe_alu_type  <= `ALU_TYPE_LOGIC;
          id_exe_alu_opsel <= `ALU_LOGIC_OR;
        end else if (func3 == `FUNC3_AND) begin
          id_exe_alu_type  <= `ALU_TYPE_LOGIC;
          id_exe_alu_opsel <= `ALU_LOGIC_AND;
        end else if (func3 == `FUNC3_SLL) begin
          id_exe_alu_type  <= `ALU_TYPE_SHIFT;
          id_exe_alu_opsel <= `ALU_SHIFT_LL;
        end else if (func3 == `FUNC3_SRL_SRA) begin
          id_exe_alu_type  <= `ALU_TYPE_SHIFT;
          id_exe_alu_opsel <= func7[5]? `ALU_SHIFT_RA: `ALU_SHIFT_RL;
        end
      end else begin
        id_exe_alu_type  <= 'h0;
        id_exe_alu_opsel <= 'h0;
      end
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_imm_data <= 32'h0;
    end else if (update_en) begin
      id_exe_imm_data <= imm_data_s;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_mem_ena <= 1'b0;
    end else if (update_en) begin
      id_exe_mem_ena <= is_mem_op;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_mem_rwn <= 1'b1;
    end else if (update_en) begin
      if (is_mem_op) begin
        id_exe_mem_rwn <= ~is_store;
      end else begin
        id_exe_mem_rwn <= 1'b1;
      end
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_mem_dtype <= 'b0;
    end else if (update_en) begin
      if (is_mem_op) begin
        id_exe_mem_dtype <= func3;
      end
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_wb_ena <= 1'b0;
    end else if (update_en) begin
      id_exe_wb_ena <= need_wb;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_wb_rd <= 'h0;
    end else if (update_en) begin
      if (need_wb) begin
        id_exe_wb_rd <= if_id_inst[11:7];
      end else begin
        id_exe_wb_rd <= 'h0;
      end
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_pc <= 32'b0;
    end else if (update_en) begin
      id_exe_pc <= if_id_pc;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_pc_inc <= 32'b0;
    end else if (update_en) begin
      id_exe_pc_inc <= pc_inc;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_exe_bench_target <= 32'b0;
    end else if (update_en) begin
      id_exe_bench_target <= pc_jmp_target;
    end
  end

  wire   jmp_req_vld;
  assign jmp_req_vld = ~(ctrl_id_flush | ctrl_id_stall);
  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_jmp_req <= 1'b0;
    end else if (update_en) begin
      id_jmp_req    <= is_jal & jmp_req_vld;
    end
  end

  always @(posedge clk, negedge rst_n) begin
    if (~rst_n) begin
      id_jmp_target <= 32'b0;
    end else if (update_en) begin
      id_jmp_target <= pc_jmp_target;
    end
  end

endmodule

